package com.sunday.common.mq.rabbit.study.spring.study.e1_Base;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
 * https://github.com/openzipkin/brave/blob/master/instrumentation/spring-rabbit/README.md
 * <p>
 * spring-amqp二个核心类RabbitAdmin和RabbitTemplate类
 * 1.RabbitAdmin类完成对Exchange，Queue，Binging的操作，在容器中管理了RabbitAdmin类的时候，可以对Exchange，Queue，Binging进行自动声明。
 * 2.RabbitTemplate类是发送和接收消息的工具类。
 * <p>
 * RabbitTemplate类是简化RabbitMQ访问的工具类（发送和接收消息）
 * 1.使用RabbitTemplate进行消息的发送。
 * 2.使用SimpleMessageListenerContainer类监听队列，进行消息的消费。
 * <p>
 * 4.1.2. 连接和资源管理
 * 虽然我们在上一节中描述的 AMQP 模型是通用的并且适用于所有实现，但当我们进入资源管理时，细节特定于代理实现。 因此，在本节中，我们将重点介绍仅存在于“spring-rabbit”模块中的代码，因为此时，RabbitMQ 是唯一受支持的实现。
 * 管理与 RabbitMQ 代理的连接的核心组件是接口。 实现的职责是提供 的实例，该实例是 的包装器。ConnectionFactoryConnectionFactoryorg.springframework.amqp.rabbit.connection.Connectioncom.rabbitmq.client.Connection
 * 选择连接工厂
 * 有三家连接工厂可供选择
 * <p>
 * PooledChannelConnectionFactory
 * ThreadChannelConnectionFactory
 * CachingConnectionFactory
 * <p>
 * 前两个是在 2.3 版中添加的。
 * 对于大多数用例，应使用 。 如果要确保严格的消息排序而不需要使用作用域内操作，则可以使用 。 如果要使用相关的发布者确认，或者希望通过其打开多个连接，则应使用 。PooledChannelConnectionFactoryThreadChannelConnectionFactoryCachingConnectionFactoryCacheMode
 * 所有三个工厂都支持简单的发布者确认。
 * 将 配置为使用单独的连接时，现在可以从版本 2.3.2 开始，将发布连接工厂配置为其他类型。 默认情况下，发布工厂的类型相同，在主工厂上设置的任何属性也会传播到发布工厂。RabbitTemplate
 */
@Slf4j
@Configuration
public class RabbitConfiguration implements InitializingBean {

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private CachingConnectionFactory rabbitConnectionFactory;

    @Value("${spring.application.name}")
    private String applicationName;

    public static final String EXCHANGE_NAME = "tracing";
    public static final String ROUTING_KEY_NAME = "router";
    public static final String QUEUE_NAME = "queue";

    @Bean
    public Declarables declarables() {
        return new Declarables(
                new Queue(QUEUE_NAME),
                new DirectExchange(EXCHANGE_NAME),
                new Binding(QUEUE_NAME, Binding.DestinationType.QUEUE, EXCHANGE_NAME, ROUTING_KEY_NAME, null)
        );
    }

    @Override
    public void afterPropertiesSet() {
        log.info("=========== AmqpAdmin : {}", amqpAdmin);
        log.info("=========== RabbitTemplate : {}", rabbitTemplate);

        /**
         * 命名连接
         * 从版本 1.7 开始，提供了用于注入 . 生成的名称用于目标 RabbitMQ 连接的特定于应用程序的标识。
         * 如果 RabbitMQ 服务器支持连接名称，则连接名称将显示在管理 UI 中。
         * 此值不必是唯一的，也不能用作连接标识符，例如，在 HTTP API 请求中。
         * 此值应该是人类可读的，并且是键下的一部分。
         * 您可以使用简单的 Lambda，如下所示：ConnectionNameStrategyAbstractionConnectionFactoryClientPropertiesconnection_name
         */
        rabbitConnectionFactory.setConnectionNameStrategy(connectionFactory -> applicationName);
        /**
         * 添加自定义客户端连接属性
         * 现在允许您访问基础连接工厂以允许，例如， 设置自定义客户端属性。 以下示例演示如何执行此操作：CachingConnectionFactory
         * connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2");
         */
        rabbitConnectionFactory.getRabbitConnectionFactory().getClientProperties().put("test1", "test2");

//        //绑定header exchange
//        Map<String,Object> headerValues = new HashMap<>();
//        headerValues.put("type",1);
//        headerValues.put("size",10);
//
//        //whereAll指定了x-match:   all参数
//        amqpAdmin.declareBinding(BindingBuilder.bind(new Queue("sltas.debug")).
//                to(new HeadersExchange("sltas.header.exchange")).whereAll(headerValues).match());
//
//        //whereAll指定了x-match:   any参数
//        amqpAdmin.declareBinding(BindingBuilder.bind(new Queue("sltas.info")).
//                to(new HeadersExchange("sltas.header.exchange")).whereAny(headerValues).match());

    }

    /**
     * PooledChannelConnectionFactory
     * 此工厂基于 Apache Pool2 管理单个连接和两个通道池。 一个池用于事务通道，另一个池用于非事务通道。
     * 池是默认配置的;提供回调以配置池;有关更多信息，请参阅 Apache 文档。GenericObjectPool
     * Apache jar 必须位于类路径上才能使用此工厂。commons-pool2
     */
//    @Bean
//    PooledChannelConnectionFactory pooledChannelConnectionFactory(CachingConnectionFactory rabbitConnectionFactory) throws Exception {
//        PooledChannelConnectionFactory pcf = new PooledChannelConnectionFactory(rabbitConnectionFactory);
//        pcf.setPoolConfigurer((pool, tx) -> {
//            if (tx) {
//                // configure the transactional pool
//            } else {
//                // configure the non-transactional pool
//            }
//        });
//        return pcf;
//    }

    /**
     * ThreadChannelConnectionFactory
     * 此工厂管理单个连接和两个连接，一个用于事务通道，另一个用于非事务通道。
     * 此工厂确保同一线程上的所有操作都使用相同的通道（只要它保持打开状态）。
     * 这有助于严格的消息排序，而无需作用域内操作。
     * 为避免内存泄漏，如果应用程序使用许多生存期较短的线程，则必须调用工厂的线程来释放通道资源。
     * 从版本 2.3.7 开始，线程可以将其通道传输到另一个线程。
     * 有关详细信息，请参阅多线程环境中的严格消息排序。ThreadLocalcloseThreadChannel()
     */


    /**
     * CachingConnectionFactory
     * 提供的第三个实现是 ，默认情况下，它建立可由应用程序共享的单个连接代理。
     * 共享连接是可能的，因为使用 AMQP 进行消息传递的“工作单元”实际上是一个“通道”（在某些方面，这类似于 JMS 中的连接和会话之间的关系）。
     * 连接实例提供了一个方法。 该实现支持缓存这些通道，并根据通道是否为事务性为通道维护单独的缓存。 创建 的实例时，可以通过构造函数提供“主机名”。
     * 您还应该提供“用户名”和“密码”属性。 若要配置通道缓存的大小（默认值为 25），可以调用该方法。
     * CachingConnectionFactorycreateChannelCachingConnectionFactoryCachingConnectionFactorysetChannelCacheSize()
     *
     * 从版本 1.3 开始，您可以配置缓存连接以及仅通道。 在这种情况下，每次调用 都会创建一个新连接（或从缓存中检索空闲连接）。
     * 关闭连接会将其返回到缓存（如果尚未达到缓存大小）。 在此类连接上创建的通道也会被缓存。
     * 在某些环境中使用单独的连接可能很有用，例如从 HA 群集使用，在 与负载均衡器配合使用，以连接到不同的集群成员等。
     * 要缓存连接，请将 设置为 。CachingConnectionFactorycreateConnection()cacheModeCacheMode.CONNECTION
     */


}
